/*
 * DKOMM LAB1
 * file: server.c
 * -----------------------------------------------------------
 * This is the solution to the task given spring 201
 * in the DKOMM course at the universitycollage of Borås.
 * It is a fully functionall web-server than handles HTTP with
 * html and basic MIME types and icnludes handling of telnet
 * connections.
 * MADE BY: FILIP & FRIENDS:
 * Filip ***
 * Viktor Lyresten
 * Tobias ***
 * Nicklas Edmark
 * Marckus Asplund
 */
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <genlib.h>
#include <strlib.h>
#include <string.h>
#include <simpio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

/*


 Vad gör monthToMonth???? Den gör om typ Dec till den korresponderande siffran från 0-11

 Vad retunerar sendFile? Antal skickade bytes???? Ja
*/


/* CONSTANTS: */

#define DEFAULT_BUFFLEN 512
#define MAX_THREADS 5
#define NOT_FOUND 404
#define OK 200
#define NOT_MODIFIED 304
#define RETURN 13
#define SERVICE_ON 1
#define SERVICE_OFF 2
#define STATUS 3
#define DOWNLOAD 4
#define END 5



/* STRUCTURES: */

typedef struct{
    string portNumber;
    string filePath;
    string logPath;
    string telnetPort;
    string telnetPassword;
} configT;

typedef struct{
	string userIP;
	char timeStr[80];
	char GMT[10];
	char cmdHTTP[80];
	char filenameHTTP[80];
	char protocolHTTP[80];
	int httpAnswer;
	int bitsDownloaded;
}*userInfoT;



/* GLOBAL VARIABLES */

bool serverOnline;



/* FUNCTIONSLIST */

int initWSA(void);
int readConfigFile(configT *configFile);
DWORD WINAPI handleThreads(LPVOID threadData);
DWORD WINAPI handleTelnetThread(LPVOID threadData);
SOCKET createListenSocket(string portNr);
int sendFile(char *fileRequest, SOCKET clientSocket, string header); 
void checkConditionalGet(char *httpReq, userInfoT userInfo, configT configFile);
int checkFileTime(char *filename, char *modTimeSince);
int saveUserInfo(userInfoT userInfo, string userFile);
string contentType(string mime);
int checkFileExist(userInfoT userInfo, configT configFile);
string createHeader(userInfoT userInfo, string mimeType);
int monthToMonth(char *month);
int getTime(userInfoT userInfo);
int recvMessageTelnet(SOCKET clientSocket, char *incMessage);
int telnetRequest(string request);
int handleRequests(int request, SOCKET clientSocket, string logFile);



/* MAIN PROGRAM: */

int main(void)
{
	int clientAddrLen;
	struct sockaddr clientAddr;
	char hostName[100], portName[100];
	configT configFile;
	SOCKET clientSocket = INVALID_SOCKET;
	SOCKET listenSocket = INVALID_SOCKET;
	SOCKET telnetSocket = INVALID_SOCKET;
	HANDLE telnetThread;
	DWORD nThreadID;
	
	//Initialize Winsock and starts the server
	if(!initWSA()) {
		printf("Failed to initialize the winsocks.\n");
		return;
	}
	serverOnline = TRUE;
	if(!readConfigFile(&configFile)) {
		printf("Failed to open the configuration file.\n");
		WSACleanup();
		return;
	}

	//Listen for a telnet connection
	telnetSocket = createListenSocket(configFile.telnetPort);
	telnetThread = CreateThread(NULL, 0, handleTelnetThread, (void *)telnetSocket, 0, NULL);
	if(telnetThread == NULL)
		printf("Failed in creating a telnet thread. \n");

	//Listen for a HTTP connection
	//listenSocket = createListenSocket(configFile.portNumber);
	clientAddrLen = sizeof(clientAddr);


	//COMMENT HERE SOON PLEASE GREAT HONOUR TO YOU
	while(TRUE)
	{
		if(serverOnline) {
			if(listenSocket == INVALID_SOCKET) {
				listenSocket = createListenSocket(configFile.portNumber);
			}

			clientSocket = accept(listenSocket,&clientAddr,&clientAddrLen);
			
			if(clientSocket == INVALID_SOCKET) {
				printf("Socket failed with error %d\n", WSAGetLastError());
				closesocket(clientSocket);
				break;
			}
			
			if(!getnameinfo(&clientAddr,clientAddrLen,hostName,100,portName,100,NI_NUMERICSERV))
				printf("Accepted incoming connection from: %s at port %s\n",hostName,portName);
			else
				printf("The function getnameinfo failed. \n");
			
			CreateThread(NULL, 0, handleThreads, (void *)clientSocket, 0, &nThreadID);

		}
		else {
			closesocket(listenSocket);
			listenSocket = INVALID_SOCKET;
		}
	}

	shutdown(listenSocket, 3);
    WSACleanup();
	getchar();
	return;
}



/* FUNCTIONS: */

/*
 * Function: readConfigFile
 * Opens the "config.txt" file that must be located in the working
 * directory of the server. Saves it's values to a structure of the type
 * configT.
 */
int readConfigFile(configT *configFile) {
    FILE* fp;

    if((fp = fopen("config.txt", "r")) == NULL) {
		printf("Couldn't open config.txt\n");
        return FALSE;
    }

    configFile->portNumber     = ReadLine(fp);
    configFile->filePath       = ReadLine(fp);
    configFile->logPath        = ReadLine(fp);
    configFile->telnetPort     = ReadLine(fp);
    configFile->telnetPassword = ReadLine(fp);

    fclose(fp);
	return TRUE;
}


/*
 * Function: initWSA()
 * Initialize winsocks.
 */
int initWSA() {
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2,2);
	if(WSAStartup(wVersionRequested, &wsaData))
		return FALSE;
	return TRUE;
}


/*
 * Function: handleThreads()
 * 
 */
DWORD WINAPI handleThreads(void * threadData) {
	SOCKET clientSocket = (SOCKET)threadData;
	userInfoT userInfo;
	configT configFile;
	struct hostent *remoteHost;
	int iResult;
	string indexHTML, header;
	char server_host[100], recvBuff[512];
	string sendMime;
	userInfo = New(userInfoT);
	
	if(!readConfigFile(&configFile)){
		printf("Failed to read file. \n");
		return FALSE;
	}

	iResult = gethostname(server_host, sizeof(server_host));
	if(iResult == SOCKET_ERROR) {
		printf("Function gethostname failed with and error: %d\n", WSAGetLastError());
		userInfo->userIP = "could not find IP adress";
	}
	else {
		remoteHost=gethostbyname(server_host);
		userInfo->userIP = inet_ntoa (*(struct in_addr *)*remoteHost->h_addr_list);
	}

	iResult = recv(clientSocket, recvBuff, 512, 0);
	if(!iResult>0) {
		printf("Function recv failed with an error: %d\n", WSAGetLastError());
		printf("Closing socket connection...\n");
		closesocket(clientSocket);
		return FALSE;
	}

	recvBuff[iResult] = '\0';
	userInfo->httpAnswer = OK;
	sscanf(recvBuff,"%s %s %s\n",userInfo->cmdHTTP,userInfo->filenameHTTP,userInfo->protocolHTTP);
	if(strcmp(userInfo->filenameHTTP, "/") == 0) {
		indexHTML = "\\index.html";
		sscanf(indexHTML, "%s", userInfo->filenameHTTP);
	}
	else
		userInfo->filenameHTTP[0] = '\\';

	iResult = checkFileExist(userInfo,configFile);
	if(iResult < 0){
		userInfo->httpAnswer = NOT_FOUND;
	}
	else {
		sendMime = contentType(userInfo->filenameHTTP);
		checkConditionalGet(recvBuff, userInfo, configFile);
	}

	iResult = getTime(userInfo);
	if(iResult > 0)
		printf("time succeeded\n");
	header = createHeader(userInfo, sendMime);

	if(userInfo->httpAnswer == OK) {
		userInfo->bitsDownloaded = sendFile(Concat(configFile.filePath,userInfo->filenameHTTP), clientSocket, header);
		if(userInfo->bitsDownloaded < 0)
			userInfo->bitsDownloaded = 0;
	}else if(userInfo->httpAnswer == NOT_FOUND) {
		string sendMessage = "<html><h1>Error 404 NOT FOUND</h1></html>";
		iResult = send(clientSocket,sendMessage,strlen(sendMessage),0);
		userInfo->bitsDownloaded = iResult;
	}

	iResult = saveUserInfo(userInfo, configFile.logPath);
	if(iResult < 0)
		printf("Failed in storing user information. \n");
	else
		printf("Successfully stored user information. \n");

	printf("Closing socket...\n");
	iResult = shutdown(clientSocket, SD_SEND);
    if(iResult == SOCKET_ERROR) {
        printf("Shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(clientSocket);
        return FALSE;
    }
	closesocket(clientSocket);
	return FALSE;
}


/*
 * Function: handleThreads()
 * 
 */
DWORD WINAPI handleTelnetThread(LPVOID threadData){
	SOCKET telnetSocket = (SOCKET)threadData;
	SOCKET clientSocket;
	configT configFile;
	int iResult, passSize;
	bool closeConnection = TRUE;

	char incMessage[100] = "";
	string sendMessage, sendPassMessage;
	sendMessage = "Hello, welcome to the telnet skit\r\n";
	sendPassMessage = "Please enter password:\r\n";

	readConfigFile(&configFile);

	while(TRUE){

		clientSocket = accept(telnetSocket, NULL, NULL);

		if(clientSocket == INVALID_SOCKET){
			printf("Socket invalid\n");
			closesocket(clientSocket);
			return 0;
		}

		printf("En client har anslutit\n");
		send(clientSocket, sendMessage, strlen(sendMessage), 0);
		send(clientSocket, sendPassMessage, strlen(sendPassMessage), 0);

		passSize = strlen(configFile.telnetPassword);
		while(1){
			iResult = recvMessageTelnet(clientSocket, incMessage);
			if(StringEqual(incMessage, configFile.telnetPassword) 
				|| StringEqual(SubString(incMessage, 1, passSize), configFile.telnetPassword))
				break;
		}
		sendPassMessage = "Welcome!\r\n";
		send(clientSocket, sendPassMessage, strlen(sendPassMessage), 0);
	
		while(closeConnection){
			sendMessage = "What would you like to do?\r\n";
			send(clientSocket, sendMessage, strlen(sendMessage), 0);
			sendMessage = "SERVICE ON, SERVICE OFF\r\nSTATUS\r\nDOWNLOAD\r\nEND\r\n\r\n";
			send(clientSocket, sendMessage, strlen(sendMessage), 0);

			recvMessageTelnet(clientSocket, incMessage);
			iResult = telnetRequest(incMessage);
			closeConnection = handleRequests(iResult, clientSocket, configFile.logPath);

		}
		closesocket(clientSocket);
		clientSocket = INVALID_SOCKET;
	}
	return 0;
}

/*
 * Function: createListenSocket(string portNr)
 * 
 */

SOCKET createListenSocket(string portNr) {
	int iResult;
	SOCKET listenSocket;
	struct addrinfo *info;

	listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(listenSocket == INVALID_SOCKET) {
		printf("Socket function failed with error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}

	iResult = getaddrinfo("localhost", portNr, NULL, &info); 
	if(iResult != 0) {
		printf("getaddrinfo failed with an error %d\n", WSAGetLastError());
		return INVALID_SOCKET;
	}else while(info->ai_family != AF_INET && info->ai_next != NULL)
		info = info->ai_next;

	iResult = bind(listenSocket, info->ai_addr, info->ai_addrlen);
	if(iResult != 0) {
		printf("Bind failed with error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}

	iResult = listen(listenSocket, SOMAXCONN);
	if(iResult == SOCKET_ERROR) {
		printf("Listening failed with an error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}
	printf("Listening socket is listening...\n");

	return listenSocket;
}

/*
 * Function: sendFile(char *fileRequest, SOCKET clientSocket, string header)
 * 
 */

int sendFile(char *fileRequest, SOCKET clientSocket, string header) {

	FILE *sendFile;
	int iSendResult,freadResult, totalSentBits = 0;
	char sendBuff[DEFAULT_BUFFLEN];

	iSendResult = send(clientSocket,header,strlen(header),0);

	sendFile = fopen(fileRequest,"rb");
	if(sendFile == NULL) {
		printf("Error, cant open file: %s\n", fileRequest);
		return -1;
	}
	while(!feof(sendFile)) {
		freadResult=fread(sendBuff,sizeof(char),DEFAULT_BUFFLEN,sendFile);
		iSendResult = send(clientSocket,sendBuff,freadResult,0);
		if (iSendResult == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            return totalSentBits;
        }
		totalSentBits += iSendResult;
	}
	fclose(sendFile);
	return totalSentBits;
}

/*
 * Function: sendFile(char *fileRequest, SOCKET clientSocket, string header)
 * 
 */

int checkFileTime(char *filename, char *modTimeSince){
	struct stat buf;
	struct tm timeSince;
	char timeStr[ 100 ] = "", buff[100];
	time_t sinceTime;
	double result;
	char year[10], mon[10], day[10], hour[10], min[10], sec[10], buffer[10];

	sscanf(modTimeSince,"%s %s %s %s %2s%*c%2s%*c%2s", buffer, day, mon, year, hour, min, sec);
	timeSince.tm_year = atoi(year)-1900;
	timeSince.tm_mon = monthToMonth(mon);
	timeSince.tm_mday = atoi(day);
	timeSince.tm_hour = atoi(hour);
	timeSince.tm_min = atoi(min);
	timeSince.tm_sec = atoi(sec);

	if(!((sinceTime = mktime(&timeSince)) != (time_t)-1))
		return OK; 
	if (stat(filename, &buf)) 
		return NOT_FOUND; 
	result = difftime(buf.st_mtime, sinceTime);
	if(result > 0)
		return NOT_MODIFIED; 
	else
		return OK; 
}

/*
 * Function: checkConditionalGet(char *httpReq, userInfoT userInfo, configT configFile)
 * 
 */

void checkConditionalGet(char *httpReq, userInfoT userInfo, configT configFile) {

	char *inputHTTP = "GET /index.html HTTP/1.1\nHost: www.someschool.edu\nUser-agent: Mozilla/4.0\nConnection: close\nIf-modified-since: Sun, 14 Feb 2013 18:22:42 GMT\n\n";
    char cmdHTTP[150];
	char filenameHTTP[150];
	char protocolHTTP[150];
	int ok = sscanf(httpReq,"%s %s %s",cmdHTTP,filenameHTTP,protocolHTTP);
	char *next = strstr(httpReq,"\n");
	while(next) {
		char name[150];
		char value[150];
		int ok = sscanf(next+1,"%s",name);
		char *space = strstr(next+1," ");
		next = strstr(next+1,"\n");
		if(space) {
		    memcpy(value,space+1,next-space-1);
		    value[next-space-1]=0;
		}
		if(ok==1) {
			if(strcmp(name,"If-modified-since:")==0) {
				if(strcmp(cmdHTTP,"GET")==0) {
					printf("I got a conditional GET with the date: %s\n",value);
					userInfo->httpAnswer = checkFileTime(Concat(configFile.filePath,userInfo->filenameHTTP),value);
				}
			}
		}
	}
}


/*
 * Function: handleRequests(int request, SOCKET clientSocket, string logFile)
 * 
 */

int handleRequests(int request, SOCKET clientSocket, string logFile) {
	string message;

	if(request == FALSE)
		return TRUE;
	else if(request == SERVICE_ON) {
		printf("Server is on\n");
		serverOnline = TRUE;
		return TRUE;
	}
	else if(request == SERVICE_OFF) {
		printf("Server is closing\n");
		serverOnline = FALSE;
		return TRUE;
	}
	else if(request == STATUS) {
		if(serverOnline == TRUE){
			message = "Server is running\r\n";
			send(clientSocket, message, strlen(message), 0);
		}else{
			message = "Server is down\r\n";
			send(clientSocket, message, strlen(message), 0);
		}
		return TRUE;
	}
	else if(request == DOWNLOAD) {
		char sendBuff[DEFAULT_BUFFLEN];
		FILE *fp;
		int i;
		if((fp = fopen(logFile, "r")) == NULL) {
			message = "Failed to open log file\r\n";
			send(clientSocket, message, strlen(message), 0);
			return TRUE;
		}

		while(fp != EOF) {
			if(fgets(sendBuff,DEFAULT_BUFFLEN,fp) == NULL)
				break;
			i = strlen(sendBuff);
			sendBuff[i] = '\r';
			sendBuff[i+1] = '\n';
			i++;
			send(clientSocket,sendBuff,i,0);
		}
		fclose(fp);
		return TRUE;
	}
	else if(request == END) {
		closesocket(clientSocket);
		return FALSE;
	}
	else
		return TRUE;
}

/*
 * Function: telnetRequest(string request)
 * 
 */

int telnetRequest(string request) {
	
	if(StringEqual(request, "SERVICE ON") 
			|| StringEqual(SubString(request, 1, strlen("SERVICE ON")), "SERVICE ON"))
			return SERVICE_ON;
	else if(StringEqual(request, "SERVICE OFF") 
			|| StringEqual(SubString(request, 1, strlen("SERVICE OFF")), "SERVICE OFF"))
			return SERVICE_OFF;
	else if(StringEqual(request, "STATUS") 
			|| StringEqual(SubString(request, 1, strlen("STATUS")), "STATUS"))
			return STATUS;
	else if(StringEqual(request, "DOWNLOAD") 
			|| StringEqual(SubString(request, 1, strlen("DOWNLOAD")), "DOWNLOAD"))
			return DOWNLOAD;
	else if(StringEqual(request, "END") 
			|| StringEqual(SubString(request, 1, strlen("END")), "END"))
			return END;
	else
		return FALSE;
}

/*
 * Function: recvMessageTelnet(SOCKET clientSocket, char *incMessage)
 * 
 */

int recvMessageTelnet(SOCKET clientSocket, char *incMessage) {

	int i = 0, rSize = 0;
	char ch;

	while (rSize += recv(clientSocket, &ch, 1, 0)) { //Den här fuckar sig om man stänger av telnet genom X 
		if (ch == (char)RETURN)
			break;

		incMessage[i] = ch;					
		send(clientSocket, (char*)ch, 1, 0);	
		i++;
	}

	incMessage[i+1] = '\0';

	return rSize;
}

/*
 * Function: createHeader(userInfoT userInfo, string mimeType)
 * 
 */

string createHeader(userInfoT userInfo, string mimeType) {
	
	string header, buffer;
	buffer = IntegerToString(userInfo->httpAnswer);
	header = "";
	header = Concat(header,userInfo->protocolHTTP);
	header = Concat(header, " ");
	header = Concat(header, buffer);
	header = Concat(header, " ");

	if(userInfo->httpAnswer == 404) {
		header = Concat(header,"Not Found");
		return header;
	}
	else if(userInfo->httpAnswer == 304) {
		header = Concat(header, "Not Modified");
		return header;
	}

	header = Concat(header,"OK");
	header = Concat(header, "\r\nContent-type: ");
	header = Concat(header, mimeType);
	header = Concat(header, "\r\n\n");
	return header;
}

/*
 * Function: saveUserInfo(userInfoT userInfo, string userFile)
 * 
 */

int saveUserInfo(userInfoT userInfo, string userFile) {
	
	FILE *fp;
	string info, temp;
	fp = fopen(userFile,"a");
	if(fp == NULL) {
		printf("Error in opening file: savefile\n");
		return FALSE;
	}

	info = Concat(userInfo->userIP, " ");
	info = Concat(info, Concat(userInfo->timeStr, "\n\""));
	info = Concat(info, Concat(userInfo->cmdHTTP, " "));
	info = Concat(info, Concat(userInfo->filenameHTTP, " "));
	info = Concat(info, Concat(userInfo->protocolHTTP,"\" "));
	temp = IntegerToString(userInfo->httpAnswer);
	info = Concat(info, Concat(temp, " "));
	temp = IntegerToString(userInfo->bitsDownloaded);
	info = Concat(info, temp);
	
	if(fputs(info,fp) != EOF);
	else{
		printf("Error in writing to the file: savefile\n");
		fclose(fp);
		return FALSE;
	}
	fputs("\n-------------------------------------------------------\n",fp);

	fclose(fp);
	return 1;
}

/*
 * Function: checkFileExist(userInfoT userInfo, configT configFile)
 * 
 */

int checkFileExist(userInfoT userInfo, configT configFile) {
	FILE *fp;

	if((fp = fopen(Concat(configFile.filePath,userInfo->filenameHTTP), "r")) == NULL)
		return FALSE;
	
	fclose(fp);
	return TRUE;

}

/*
 * Function: contentType(string filename)
 * 
 */

string contentType(string filename) {
	    string mime = SubString(filename, strlen(filename)-4, strlen(filename));

        if(strcmp(mime, ".jpg") == 0)
                return "image/jpeg";
        else if(strcmp(mime, "jpeg") == 0)
                return "image/jpeg";
        else if(strcmp(mime, "html") == 0 || strcmp(mime, ".htm") == 0)
                return "text/html";
        else if(strcmp(mime, ".png") == 0)
                return "image/png";
        else if(strcmp(mime, ".gif") == 0)
                return "image/gif";
        else if(strcmp(mime, ".zip") == 0)
                return "application/x-zip-compressed";
        else if(strcmp(mime, ".ico") == 0)
                return "image/ico";
        else return "application/octet-stream";
}

/*
 * Function: monthToMonth(char *month)
 * 
 */

int monthToMonth(char *month) {

	if(strcmp(month, "Jan") == 0)
		return 0;
	else if(strcmp(month, "Feb") == 0)
		return 1;
	else if(strcmp(month, "Mar") == 0)
		return 2;
	else if(strcmp(month, "Apr") == 0)
		return 3;
	else if(strcmp(month, "May") == 0)
		return 4;
	else if(strcmp(month, "Jun") == 0)
		return 5;
	else if(strcmp(month, "Jul") == 0)
		return 6;
	else if(strcmp(month, "Aug") == 0)
		return 7;
	else if(strcmp(month, "Sep") == 0)
		return 8;
	else if(strcmp(month, "Oct") == 0)
		return 9;
	else if(strcmp(month, "Nov") == 0)
		return 10;
	else if(strcmp(month, "Dec") == 0)
		return 11;
	else
		return -1;
}

/*
 * Function: getTime(userInfoT userInfo)
 * 
 */

int getTime(userInfoT userInfo) {

	time_t gTime, lTime, bTime;
	struct tm *gTimeS, *lTimeS;
	double result;
	string temp;
	time(&bTime);
	strftime(userInfo->timeStr, 80, "[ %d/%b/%Y:%H:%M:%S ", localtime( &bTime));

	time(&gTime);
	gTimeS = gmtime(&gTime);
	if((gTime = mktime(gTimeS)) == (time_t)-1) {
		strcat(userInfo->timeStr, "-1]");
		return FALSE;
	}
	
	
	time(&lTime);
	lTimeS = localtime(&lTime);
	if((lTime = mktime(lTimeS)) == (time_t)-1) {
		strcat(userInfo->timeStr, "-1]");
		return FALSE;
	}

	result = difftime(lTime,gTime);
	temp = IntegerToString((int)((result/60)/60));
	strcat(userInfo->timeStr, temp);
	strcat(userInfo->timeStr, "]");

	return TRUE;
}